package com.zdp.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zdp.commons.JsonUtils;
import com.zdp.security.filter.RestAuthenticationFilter;

import com.zdp.security.jwt.JwtFilter;
import com.zdp.security.userdetails.UserDetailsServiceImpl;
import com.zdp.security.userdetails.UserDetailsPasswordServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Profiles;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @author sesshomaru
 * @date 2021/7/2
 *
 * Rest风格认证
 */
@Configuration
@RequiredArgsConstructor
@Slf4j
@Order(99)
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final ObjectMapper objectMapper;

    private final DataSource dataSource;

    private final UserDetailsServiceImpl userDetailsService;

    private final UserDetailsPasswordServiceImpl userDetailsPasswordService;

    private final JwtFilter jwtFilter;

    // 用于配置那些路劲需要被拦截处理
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 只关注这些"/authorize/**", "/admin/**", "/api/**" 接口
                .requestMatchers(req -> req.mvcMatchers("/authorize/**", "/admin/**", "/api/**"))
                // session设置为无状态的，因为Rest用不到session
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeRequests(authorizeRequests -> authorizeRequests
                        .antMatchers("/authorize/**").permitAll()
                        .antMatchers("/admin/**").hasRole("ADMIN")
                        // 访问需要user或sign权限
                        // .antMatchers("/api/user/**").access("hasRole('USER') or hasRole('SIGN')")
                        // 访问/api/user/{username}中的{username}必须和认证对象中的username一致且必须是user对象才可以访问
                        //.antMatchers("/api/user/{username}").access("hasRole('USER') and authentication.name.equals(#username)")
                        // 和上面的一样只是缓存使用bean方法的方式验证
                        .antMatchers("/api/user/{username}").access("hasRole('USER') and @userService.isValidUser(authentication, #username)")
                        .antMatchers("/api/user/manager").hasRole("MANAGER")
                        .anyRequest().authenticated())
                .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 配置跨域
                .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(jwtFilter,UsernamePasswordAuthenticationFilter.class)
                // 关闭csrf，因为Rest用不到csrf
                .csrf(csrf ->csrf.disable())
                // 启用认证头不然被拦截的接口无法访问，不过需要携带认证头信息
                .httpBasic(Customizer.withDefaults())
        ;
    }

    // 允许或者拒绝一些请求不同通过认证和授权即可访问资源
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 访问/error/**不做过滤
        web
                .ignoring()
                .antMatchers( "/error/**", "/authorize/**","/h2-console/**");
    }

    /**
     * 替代在yml配置的登录时输入的用户名和密码
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //单个AuthenticationProvider

        auth
                .userDetailsService(userDetailsService) // 配置 AuthenticationManager 使用 userService
                .passwordEncoder(passwordEncoder())
                .userDetailsPasswordManager(userDetailsPasswordService); // 配置密码自动升级服务

  /*
        配置多个AuthenticationProvider
        auth.authenticationProvider(lDAPMultiAuthenticationProvider());
        auth.authenticationProvider(daoAuthenticationProvider());*/
    }

/*    @Bean
    public LDAPMultiAuthenticationProvider lDAPMultiAuthenticationProvider() {
        LDAPMultiAuthenticationProvider lDAPMultiAuthenticationProvider = new LDAPMultiAuthenticationProvider();
        return lDAPMultiAuthenticationProvider;
    }*/


    /**
     * 我们在 Spring Boot 中有几种其他方式配置 CORS
     * 参见 https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-cors
     * Mvc 的配置方式见 WebMvcConfig 中的代码
     *
     * @return CorsConfigurationSource
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // 允许跨域访问的主机
        configuration.setAllowedOrigins(Collections.singletonList("http://localhost:4001"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.addExposedHeader("X-Authenticate");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsPasswordService(userDetailsPasswordService);
        return daoAuthenticationProvider;
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        // 默认编码算法的 Id
        val idForEncode = "bcrypt";
        // 要支持的多种编码器
        Map encoders = new HashMap<>();
        encoders.put(idForEncode, new BCryptPasswordEncoder());
        encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));

        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }

    // 实例化自定义的Filter
    private RestAuthenticationFilter restAuthenticationFilter() throws Exception {
        RestAuthenticationFilter filter = new RestAuthenticationFilter(objectMapper);
        filter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(jsonAuthenticationFailureHandler());
        filter.setAuthenticationManager(authenticationManager());
        // filter应用在哪个路口上
        filter.setFilterProcessesUrl("/authorize/login");
        return filter;
    }

    private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
        return (req, res, exp) -> {
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            res.setContentType(MediaType.APPLICATION_JSON_VALUE);
            res.setCharacterEncoding("UTF-8");
            Map<String, String> errData = new HashMap<>();
            errData.put("title", "认证失败");
            errData.put("details", exp.getMessage());
            res.getWriter().println(JsonUtils.objectToJson(errData));
        };
    }

    private AuthenticationSuccessHandler jsonAuthenticationSuccessHandler() {
        return (req, res, auth) -> {
            res.setStatus(HttpStatus.OK.value());
            // 将auth对象转换为json返回回去
            res.getWriter().println(JsonUtils.objectToJson(auth));
            log.debug("认证成功");
        };
    }

    /**
     * 配置权限层级
     * @return
     */
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_AMIN > ROLE_MANAGER\nROLE_MANAGER > ROLE_USER");
        return roleHierarchy;
    }
}
